﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO.Ports;
using SSPComm.SSPCommands;
using System.Diagnostics;
using System.Collections;

namespace SSPComm.SSPTransport
{
    internal class SSPChannel
    {
        private static Byte SYNC_BYTE = 0x7F;
        private static Int32 SSP_TIMEOUT_READ = 1000;
        private static Int32 SSP_TIMEOUT_WRITE = 1000;
        private readonly TraceListener _logger;
        SerialPort _port;
        
        public SSPChannel(String inComPort)
        {
            _port = new SerialPort(inComPort, 9600, Parity.None, 8, StopBits.Two);
            _port.ReadTimeout = SSP_TIMEOUT_READ;
            _port.WriteTimeout = SSP_TIMEOUT_WRITE;
            _port.RtsEnable = false;
            _port.Handshake = Handshake.None;
            _port.DiscardNull = false;
            _port.DtrEnable = false;
            _logger = new TextWriterTraceListener(String.Concat(ToString(),".txt"));
        }

        internal Boolean IsOpen
        {
            get
            {
                return _port.IsOpen;
            }
        }

        public SSPCommand SendAndReceive(SSPCommand inCmd)
        {
            SendCommand(inCmd);
            return ReceiveCommand();
        }

		private void SendCommand(SSPCommand inCmd)
		{
			try
			{
				Byte[] cmdStuffed = SSPTransportStuff.InsertByteStuff(inCmd.RawData);
				Byte[] data2send = new Byte[cmdStuffed.Length + 1];
				data2send[0] = SYNC_BYTE;
				cmdStuffed.CopyTo(data2send, 1);
				_port.Write(data2send, 0, data2send.Length);

				_logger.WriteLine(String.Format("{0}\t>\t{1}", GetTime(), GetByteArray(data2send)), this.ToString());
			}
			catch (System.IO.IOException ioexc)
			{
				try
				{
					_port.Close();
				}
				catch { };
				_logger.WriteLine(String.Format("{0}\t>\t{1}", GetTime(), ioexc.Message), this.ToString());
				throw;
			}
		}

        #region stuff
        private static String GetByteArray(Byte[] inData)
        {
            StringBuilder builder = new StringBuilder(inData.Length * 3);
            IEnumerator en = inData.GetEnumerator();
            while (en.MoveNext())
            {
                builder.Append(String.Format("0x{0} ", ((Byte)en.Current).ToString("X2")));
            }
            return builder.ToString();
        }

        private static String GetTime()
        {
            return DateTime.Now.ToString("ss.fff");
        }

        #endregion

        private SSPCommand ReceiveCommand()
        {

            int readedByte = 0;
            bool sync = false;
            Boolean packetReceived = false;
            List<Byte> result = new List<Byte>(10);
            while (!packetReceived)
            {
                readedByte = _port.ReadByte();
                if (readedByte == -1)
                {
                    break;
                }
                _logger.WriteLine(String.Format("{0}\t<\t{1}", GetTime(), GetByteArray(new Byte[] { (Byte)readedByte })), this.ToString());
                if (readedByte == SYNC_BYTE)
                {
                    sync = true;
                }
                else if (sync)
                {
                    result.Add((Byte)readedByte);
                    readedByte = _port.ReadByte();
                    if (readedByte == -1)
                    {
                        break;
                    }
                    result.Add((Byte)readedByte);
                    //CRC LEN
                    Int32 waitCounter = result[1] + 2;
                    if (readedByte == SYNC_BYTE)
                    {
                        waitCounter++;
                    }

                    Boolean syncPacket = false;
                    packetReceived = true;
                    while (waitCounter > 0)
                    {

                        readedByte = _port.ReadByte();

                        if (readedByte == -1)
                        {
                            break;
                        }
                        result.Add((Byte)readedByte);
                        if (readedByte == SYNC_BYTE)
                        {
                            if (!syncPacket)
                            {
                                syncPacket = true;
                                waitCounter++;
                            }
                            else
                            {
                                syncPacket = false;
                            }
                        }
                        waitCounter--;
                    }
                }
            }

            _logger.WriteLine(String.Format("{0}\t<\t{1}", GetTime(), GetByteArray(result.ToArray())), this.ToString());

            Byte[] resultwoStuff = SSPTransportStuff.DeleteByteStuff(result.ToArray());

            if (!SSPTransportStuff.IsCRCValid(resultwoStuff))
            {
                throw new Exception("crc");
            }

            _port.ReadExisting();

            return new SSPCommand(resultwoStuff);
        }

        public void Open()
        {
            _logger.WriteLine(String.Format("{0}\to",GetTime()), this.ToString());

            try
            {
                _port.Open();
            }
            catch (Exception exc)
            {
                _logger.WriteLine(String.Format("{0}\tFo", GetTime()), this.ToString());
                throw exc;
            }
            _logger.WriteLine(String.Format("{0}\tO",GetTime()), this.ToString());

        }

        public void Close()
        {
            _logger.WriteLine(String.Format("{0}\tc",GetTime()), this.ToString());
            try
            {
                _port.Close();
            }
            catch (Exception exc)
            {
                _logger.WriteLine(String.Format("{0}\tFc",GetTime()), this.ToString());
                throw exc;
            }
            _logger.WriteLine(String.Format("{0}\tC",GetTime()), this.ToString());
            _logger.Flush();
        }

        public override string ToString()
        {
            return String.Format("SSPChannel-{0}", _port.PortName);
        }
    }
}
